package jalgebrava.slp;


import jalgebrava.group.Group;

import java.util.ArrayList;
import java.util.List;

public final class SLP {
	public static abstract class Instr {
		protected abstract <G> G interpret(int offset, Group<G> g, List<G> gs);
		public final static Instr inv(int i) {
			return new Inv(i);
		}
		public final static Instr mul(int i1, int i2) {
			return new Mul(i1,i2);
		}
	}
	public static final class Inv extends Instr {
		private final int index;
		private Inv(int i) {
			index = i;
		}
		@Override
		protected <G> G interpret(int offset, Group<G> g, List<G> gs) {
			return g.inv(gs.get(offset+index));
		}		
	}
	public static final class Mul extends Instr {
		private final int index1;
		
		private final int index2;
		private Mul(int i1, int i2) {
			index1 = i1;
			index2 = i2;
		}
		
		@Override
		protected <G> G interpret(int offset, Group<G> g, List<G> gs) {
			G arg1 = gs.get(offset+index1);
			G arg2 = gs.get(offset+index2);
			G result = g.mul(arg1, arg2);
			System.out.println("Mul [" + index1 + "," + index2 + "]" + arg1 + " * " + arg2 + " = " + result);
			return result;
		}
		
	}
	
	private final Instr[] instructions;
	public SLP(Instr... ins) {
		this.instructions = ins;
	}
	public <G> G execute(Group<G> group, G[] gs) {
		return SLP.execute(group, gs, instructions);
	}
	public static <G> G execute(Group<G> group, G[] gs, Instr[] instrs) {
		if (instrs.length == 0) {
			return group.unit();
		}
		ArrayList<G> scratch = new ArrayList<G>(gs.length + instrs.length + 1);
		int l = gs.length;
		for (int i = 0;i<l;i++) {
			scratch.add(gs[(l - 1) - i]);
		}
		
		for (Instr i: instrs) {
			G x = i.interpret(l-1, group, scratch);
			scratch.add(x);
		}
		System.out.println(scratch);
		// last element is the result.
		
		return scratch.get(gs.length + instrs.length - 1);
	}
}
